package com.ytf.test.third;

import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author: YTF
 * @date: 2024/9/12 11:01
 * @version: 1.0
 * @description: 随机数操作 是否使用种子seed
 */
public class RandomOps {
    public static void main(String[] args) {
        Random randomNoSeed = new Random();
        System.out.println("不设置种子 randomNoSeed: ");
        for (int i = 0; i < 10; i++) {
            int randomInt = randomNoSeed.nextInt(10);
            System.out.print(i+":"+randomInt+" ");
        }
        Random randomSeed = new Random(47);
        System.out.println("\n设置种子 randomSeed: ");
        for (int i = 0; i < 10; i++) {
            int randomInt = randomSeed.nextInt(10);
            System.out.print(i+":"+randomInt+" ");
        }

        /**
         * output
         * 第一次
         * 不设置种子 randomNoSeed:
         *  0:0 1:6 2:3 3:2 4:8 5:4 6:9 7:1 8:1 9:8
         *  设置种子 randomSeed:
         *  0:8 1:5 2:3 3:1 4:1 5:9 6:8 7:0 8:2 9:7
         * 第二次
         *  不设置种子 randomNoSeed:
         *  0:9 1:6 2:9 3:1 4:5 5:4 6:0 7:4 8:4 9:3
         *  设置种子 randomSeed:
         *  0:8 1:5 2:3 3:1 4:1 5:9 6:8 7:0 8:2 9:7
         * 总结：
         * 设置种子seed后，随机变成伪随机了
         * nextInt();和nextInt(10);差距很大，默认是32位所有数字都有可能，一个是限制在0-10之间包括0，但不包括10。
         */
        /**
         * 扩展思考：
         * 1.种子是怎么实现的
         * 2.怎么参数随机数的
         * Output：
         * 扩展思考 RandomOps模拟实验：
         * seed: 25214903874
         * 0 oldseed: 25214903874 nextseed: 204664898672165 (int)(nextseed >>> (48 - bits)): 1561469258 r: 1561469258 false r: 8 result:8
         * 1 oldseed: 204664898672165 nextseed: 112541113445324 (int)(nextseed >>> (48 - bits)): 858620555 r: 858620555 false r: 5 result:5
         * 2 oldseed: 112541113445324 nextseed: 149447861054951 (int)(nextseed >>> (48 - bits)): 1140196693 r: 1140196693 false r: 3 result:3
         * 3 oldseed: 149447861054951 nextseed: 15034202363238 (int)(nextseed >>> (48 - bits)): 114701861 r: 114701861 false r: 1 result:1
         * 4 oldseed: 15034202363238 nextseed: 45094136922233 (int)(nextseed >>> (48 - bits)): 344040961 r: 344040961 false r: 1 result:1
         * 5 oldseed: 45094136922233 nextseed: 162691865586064 (int)(nextseed >>> (48 - bits)): 1241240429 r: 1241240429 false r: 9 result:9
         * 6 oldseed: 162691865586064 nextseed: 53052030063195 (int)(nextseed >>> (48 - bits)): 404754868 r: 404754868 false r: 8 result:8
         * 7 oldseed: 53052030063195 nextseed: 117378934477514 (int)(nextseed >>> (48 - bits)): 895530200 r: 895530200 false r: 0 result:0
         * 8 oldseed: 117378934477514 nextseed: 145410558852109 (int)(nextseed >>> (48 - bits)): 1109394522 r: 1109394522 false r: 2 result:2
         * 9 oldseed: 145410558852109 nextseed: 207545461309332 (int)(nextseed >>> (48 - bits)): 1583446207 r: 1583446207 false r: 7 result:7
         *
         * 总结：
         * 种子设置后变成伪随机是因为线性同余生成器这个伪随机算法实现的。
         * 种子是否设置代码都会生成，不同的是初始种子自己设置是固定的不设置系统随机。
         * 两个问题实际上是一个问题。
         *
         */
        System.out.println("\n扩展思考 RandomOps模拟实验：");
        // RandomOps randomOps = new RandomOps();
        RandomOps randomOps = new RandomOps(47);
        for (int i = 0; i < 10; i++) {
            System.out.print(i +" ");
            int randomInt = randomOps.nextInt(10);
            // int randomInt = randomOps.nextInt(32);
            System.out.println(" result:"+randomInt+" ");
        }
    }

    private final AtomicLong seed;

    private static final long multiplier = 0x5DEECE66DL;
    private static final long addend = 0xBL;
    private static final long mask = (1L << 48) - 1;

    public RandomOps() {
        //! this.seed = new AtomicLong(); 这个是有问题的，因为第一次初始化永远是0 所以结果就是每次都一样

        // 通过系统时间让初始值不在为0。源码是有做相关安全操作的，这里进行了简化验证
        this(System.nanoTime());
    }

    public RandomOps(long seed) {
        seed = initialScramble(seed);
        System.out.println("seed: "+ seed);
        this.seed = new AtomicLong(seed);
    }

    /**
     * 线性同余生成器作为一种经典的伪随机数生成算法，因其简单高效而被广泛应用。
     * 然而，在选择参数时需要特别注意，以避免生成的随机数序列质量不佳。
     * (伪随机不适合的场景就是必须随机的地方比如：密码，科学模拟和试验......)
     *
     * @param seed
     * @return
     */
    private static long initialScramble(long seed) {
        return (seed ^ multiplier) & mask;
    }
    public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException("bound must be positive");

        int r = next(31);
        System.out.print(" r: "+r);
        int m = bound - 1;
        System.out.print(" bound: "+bound+" m: "+m+" (bound & m):"+(bound & m));
        if ((bound & m) == 0) { // i.e., bound is a power of 2
            r = (int)((bound * (long)r) >> 31);
            System.out.print(" true r: "+r);
        } else {
            for (int u = r;
                 u - (r = u % bound) + m < 0;
                 u = next(31)){
                System.out.print(" u:"+u);
            }
                ;
            System.out.print(" false r: "+r);
        }
        return r;
    }

    protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
            System.out.print("oldseed: "+oldseed+ " nextseed: "+nextseed);
        } while (!seed.compareAndSet(oldseed, nextseed));
        int i = (int)(nextseed >>> (48 - bits));
        System.out.print(" (int)(nextseed >>> (48 - bits)): "+i);
        return i;
    }

}
